// Copyright (c) Microsoft Corporation. All rights reserved.

namespace Microsoft.Tools.CodeRunner
{
	using System;
	using System.Reflection;
	using System.Security.Permissions;
	using System.Diagnostics;
	using System.IO;
	using System.Text;
	using System.Runtime.Remoting;
	using System.Runtime.InteropServices;
	using ThisNamespace = Microsoft.Tools.CodeRunner;

	/// <summary>
	/// Environment class.
	/// </summary>
	public sealed class ScriptEnvironment : MarshalByRefObject
	{
		#region Class Data

		private static ParsedPath scriptPath;
		private static bool runningFromCache;
		private static ParsedPath cachePath = null;
		private static ParsedPath runtimeRspPath;
		private static ParsedPath sharedRspPath;
		private static ParsedPath scriptRspPath;
		private static ParsedPath[] additionalReferences = null;
		private static string clrVersionDirectory = null;
		private static string cacheRootDirectory = null;

		#endregion

		#region Constructors

		private ScriptEnvironment()
		{
		}

		static ScriptEnvironment()
		{
			// Getting the script path right is a little complicated.  At this point either (a) scaffold.exe or csr.exe will set the 
			// name of the scriptPath directly after this constructor is called or (b) the value was passed in for a cached script via the 
			// AppDomain parameter ScriptPath or (c) the script is being run from inside VS, in which case we can deduce the script 
			// path from a combination of the main module name and the directory one level up from the Scaffold temporary directory.
			ParsedPath path = (ParsedPath)AppDomain.CurrentDomain.GetData("ScriptPath");
			
			if (path != null)
				scriptPath = path;
			else
			{
				// If the ScriptPath wasn't already set, assume that we are running the compiled script frow within VS
				ParsedPath modulePath = new ParsedPath(Process.GetCurrentProcess().MainModule.FileName.ToLower());

				// If running from either of these two processes, they will set the script path via the ScriptPath property
				if (modulePath.FileAndExtension.CompareTo("scaffold.exe") != 0 &&
					modulePath.FileAndExtension.CompareTo("csr.exe") != 0)
				{
					// So, if this is process is being run from a scaffolded project, the directory ends in something like ...\Scaffold_1234\bin\debug.  
					// So if we go up 3 directories we should find the script.
					if (modulePath.DirectoryDepth >= 3)
					{
						ParsedPath parentPath = modulePath.MakeParentPath(-3);

						// Look for a .csr, which is most likely					
						path = new ParsedPath(parentPath.RootAndDirectory + modulePath.File + ".csr");
						if (!File.Exists(path))
						{
							// OK, maybe it's a .cs file
							path = new ParsedPath(parentPath.RootAndDirectory + modulePath.File + ".cs");
							if (!File.Exists(path))
							{
								// Uh, dunno what it is.  We could be nuts here and start searching, but is it really worth it?
								path = null;
							}
						}
					}

					if (path == null)
					{
						// Worst case, we just use the module name with the .csr extension
						path = new ParsedPath(modulePath.RootDirectoryAndFile + ".csr");
					}

					scriptPath = path;
				}
			}

			runningFromCache = (AppDomain.CurrentDomain.FriendlyName.CompareTo("CsrDomain") == 0 ? true : false);
		}

		#endregion

		#region Class Properties

		/// <summary>
		/// Turns a fully qualified script name into an executable name suitable for inserting into the script cache
		/// </summary>
		/// <param name="scriptPath"></param>
		/// <returns></returns>
		public static string CachifySourceName(ParsedPath scriptPath)
		{
			if (!scriptPath.IsFull)
				throw new ArgumentException("Must be fully qualified path name", "scriptPath");
				
			StringBuilder sb = new StringBuilder(scriptPath.RootDirectoryAndFile + ".exe");
					
			sb.Replace('\\', '_');
			sb.Replace(':', '_');
			sb.Replace(' ', '_');
					
			return sb.ToString();
		}

		/// <summary>
		/// Returns the root directory of the script cache
		/// </summary>
		public static string CacheRootDirectory
		{
			get
			{
				if (cacheRootDirectory == null)
				{
					Assembly assembly = Assembly.GetExecutingAssembly();

					if (assembly == null) 
					{
						assembly = Assembly.GetCallingAssembly();
					}

					// Get copyright information
					string company = String.Empty;
					object[] companyAttributes = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);

					if (companyAttributes.Length > 0) 
					{
						AssemblyCompanyAttribute companyAttribute = (AssemblyCompanyAttribute) companyAttributes[0];
						
						company = companyAttribute.Company;
					}
					
					// That was a lot of bother just to get the company name...
					
					cacheRootDirectory = (new ParsedPath(System.Environment.GetFolderPath(
						System.Environment.SpecialFolder.CommonApplicationData), 
						PathType.Directory).RootAndDirectory) + company + @"\CodeRunner\cache\";
				}
						
				return cacheRootDirectory;
			}
		}

		/// <summary>
		/// Get the sub-directory for the currently executing .NET runtime.
		/// </summary>
		/// <exception cref="System.Security.SecurityException">The path cannot be set except by the this utility.</exception>
		public static string ClrVersionSubDirectory
		{
			get
			{
				if (clrVersionDirectory == null)
					clrVersionDirectory = String.Format("v{0}.{1}.{2}", 
						System.Environment.Version.Major.ToString(),
						System.Environment.Version.Minor.ToString(),
						System.Environment.Version.Build.ToString());
					
				return clrVersionDirectory;
			}
		}
		
		/// <summary>
		/// Get the path of the currently executing script.rsp file.
		/// </summary>
		public static ParsedPath ScriptRspPath
		{
			get 
			{
				if (scriptRspPath == null)
					scriptRspPath = new ParsedPath(scriptPath.RootDirectoryAndFile + ".rsp");
				
				return scriptRspPath;
			}
		}
		
		/// <summary>
		/// Get the path of the shared csc.rsp file.
		/// </summary>
		public static ParsedPath SharedRspPath
		{
		    get 
		    {
				// TODO: Use the registry to find this instead?
				if (sharedRspPath == null)
					sharedRspPath = new ParsedPath(
						new ParsedPath(Process.GetCurrentProcess().MainModule.FileName).RootAndDirectory + "csc.rsp");
					
		        return sharedRspPath;
		    }
		}

		/// <summary>
		/// Get the paths of the runtime's csc.rsp file.
		/// </summary>
		public static ParsedPath RuntimeRspPath
		{
			get 
			{
				if (runtimeRspPath == null)
					runtimeRspPath = new ParsedPath(new ParsedPath(
						RuntimeEnvironment.GetRuntimeDirectory(), PathType.Directory).RootAndDirectory + "csc.rsp");
					
				return runtimeRspPath;
			}
		}

		/// <summary>
		/// Contains a list of additional references specified for this assembly on the command line
		/// </summary>
		public static ParsedPath[] AdditionalReferences
		{
			get
			{
				return additionalReferences;
			}
			[StrongNameIdentityPermissionAttribute(SecurityAction.Demand, PublicKey =
			   "0x00240000048000009400000006020000002400005253413100040000010001003974045a0f4db8" +
			   "bdb8d211e4385a9b670212fcbd3752b361a92055bd1c61734423f34a504c5806974cd83789ab18" +
			   "96235e290aad975f62e365fb21e2a0615e6239c0020ef50186ae31530fa92dddf5f7dd908601d0" +
			   "f994fcdf8c6c722c59081ea3d90be469ad2338c40b90eee207e865047615aed3cb6495af11cf91" +
			   "643a70cf")]
			set 
			{
				additionalReferences = value;
			}
		}
	
		/// <summary>
		/// Get the path of the currently running script if the script is being run from the runner,
		/// OR the path of for main module of the currently executing process if not.
		/// </summary>
		/// <exception cref="System.Security.SecurityException">The path can only be set by the CSR utility.</exception>
		public static ParsedPath ScriptPath
		{
			get 
			{
				return scriptPath;
			}
			[StrongNameIdentityPermissionAttribute(SecurityAction.Demand, PublicKey = 
				"0x00240000048000009400000006020000002400005253413100040000010001003974045a0f4db8" +
				"bdb8d211e4385a9b670212fcbd3752b361a92055bd1c61734423f34a504c5806974cd83789ab18" +
				"96235e290aad975f62e365fb21e2a0615e6239c0020ef50186ae31530fa92dddf5f7dd908601d0" +
				"f994fcdf8c6c722c59081ea3d90be469ad2338c40b90eee207e865047615aed3cb6495af11cf91" +
				"643a70cf")]
			set 
			{
				scriptPath = value;
			}
		}

		/// <summary>
		/// This value indicates whether the script is being run from a cached compiled executable or not.
		/// </summary>summary>
		public static bool RunningFromCache
		{
		    get 
		    {
		        return runningFromCache;
		    }
			[StrongNameIdentityPermissionAttribute(SecurityAction.Demand, PublicKey =
			   "0x00240000048000009400000006020000002400005253413100040000010001003974045a0f4db8" +
			   "bdb8d211e4385a9b670212fcbd3752b361a92055bd1c61734423f34a504c5806974cd83789ab18" +
			   "96235e290aad975f62e365fb21e2a0615e6239c0020ef50186ae31530fa92dddf5f7dd908601d0" +
			   "f994fcdf8c6c722c59081ea3d90be469ad2338c40b90eee207e865047615aed3cb6495af11cf91" +
			   "643a70cf")]
			set
		    {
		        runningFromCache = value;
		    }
		}
		
		/// <summary>
		/// Get the directory of the compiled script cache.
		/// </summary>
		public static ParsedPath CachePath
		{
			get 
			{
				if (cachePath == null)
					cachePath = new ParsedPath(CacheRootDirectory + ClrVersionSubDirectory + @"\");

				return cachePath;
			}
		}

		#endregion
	}
}
